﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using PerformanceAssessment.Models;
using System.Web.Security;
using System.Data;
using PagedList;
using System.Net;
using System.Configuration;

namespace PerformanceAssessment.Controllers
{
    public class AssessmentsController : Controller
    {
        private PerformanceAssessmentDB db = new PerformanceAssessmentDB();
        
        [Authorize]
        public ActionResult Index(int? assessmentPeriods, string searchString, int? status, int? page, int? teams)
        {
            List<AssessmentPeriod> periodList;
            List<Employee> employeeList;
            var teamList = db.Teams.OrderBy(p => p.Name).ToList();
            UserModel user = UserModel.GetCurrentUser();

            if (user.UserType == UserType.Administrator)
            {
                //Screen for administrator
                periodList = db.AssessmentPeriods.OrderByDescending(p => p.FromDate).ToList();
                employeeList = db.Employees.ToList();
            }
            else if (user.UserType == UserType.Employee)
            {
                //Screen for employee
                periodList = (from p in db.AssessmentPeriods
                              from t in p.Teams
                              join e in db.Employees on t.ID equals e.IdTeam
                              where e.ID == user.ID
                              orderby p.FromDate descending
                              select p).ToList();
                employeeList = new List<Employee>() { db.Employees.FirstOrDefault(e => e.ID == user.ID) };
            }
            else
            {
                //Screen for manager
                periodList = (from p in db.AssessmentPeriods
                              from t in p.Teams
                              join e in db.Employees on t.ID equals e.IdTeam
                              where e.IdManager == user.ID
                              orderby p.FromDate descending
                              select p).Distinct().ToList();

                employeeList = db.Employees.Where(e => e.IdManager == user.ID).OrderBy(e => e.FirstName).ToList();
            }
            
            ViewBag.assessmentPeriods = new SelectList(periodList, "ID", "Description", assessmentPeriods);
            ViewBag.teams = new SelectList(teamList, "ID", "Name", teams);
            ViewBag.searchString = searchString;
            var periodIds = assessmentPeriods == null ? periodList.Select(p => p.ID).ToList() : new List<int>() { assessmentPeriods.Value };
            var teamIds = teams == null ? teamList.Select(p => p.ID).ToList() : new List<int>() { teams.Value };

            //The list of employees is formatted as a select list to show the full name
            var employees = from u in employeeList
                            join t in teamList on u.IdTeam equals t.ID
                            select new EmployeeList
                            {
                                ID = u.ID,
                                Team = t.Name,
                                Ced = u.Ced,
                                Name = u.FirstName + " " + u.FirstLastName + " " + u.SecondLastName,
                                Active = u.Active,
                                Email = u.Email,
                                StartDate = u.StartDate
                            };

            if (!String.IsNullOrEmpty(searchString))
            {
                employees = employees.Where(s => s.Name.ToLower().Contains(searchString.ToLower()));
            }

            //All periods selected
            var employeeIds = employees.Select(e => e.ID).ToList();

            IQueryable<Assessment> queryAssessments = db.Assessments.Where(a => periodIds.Contains(a.IdAssessmentPeriod)
                                                                                && employeeIds.Contains(a.IdEmployee)
                                                                                && teamIds.Contains(a.Employee.IdTeam)).
                                                                                OrderByDescending(a => a.FromDate);

            var statusL = new List<SelectListItem>();
            statusL.Add(new SelectListItem { Text = "Pending Approval", Value = "1" });
            ViewBag.status = new SelectList(statusL, "Value", "Text", status);
            if (status != null)
            {
                int[] approvalAssessments = {(int)AssessmentStatus.AutoEvaluationCompleted, 
                                                (int)AssessmentStatus.DisagreementSolved, 
                                                (int)AssessmentStatus.ManagerAssessmentCompleted};
                queryAssessments = queryAssessments.Where(a => approvalAssessments.Contains(a.IdAssessmentStatus));
            }

            IEnumerable<Assessment> pendingAssessments = null;
            if (user.UserType == UserType.Employee)
            {
                pendingAssessments = (from p in periodList
                                     where !(from a in db.Assessments
                                             where a.IdEmployee == user.ID
                                             select a.IdAssessmentPeriod).Contains(p.ID)
                                             && periodIds.Contains(p.ID)
                                     select new Assessment()
                                     {
                                         ID = 0,
                                         IdAssessmentPeriod = p.ID,
                                         FromDate = p.FromDate,
                                         ToDate = p.ToDate,
                                         EmployeeComments = null,
                                         ManagerComments = null,
                                         IdAssessmentStatus = 0,
                                     }).ToList();
            }

            int pageNumber = (page ?? 1);
            int pageSize; 
            int.TryParse(ConfigurationManager.AppSettings["PageSize"], out pageSize);
            IPagedList<Assessment> assessments;
            if (user.UserType == UserType.Employee && pendingAssessments != null && pendingAssessments.Any())
            {
                IEnumerable<Assessment> totalAssessments = queryAssessments.ToList().Union(pendingAssessments);
                assessments = new StaticPagedList<Assessment>(totalAssessments.Skip((pageNumber - 1) * pageSize).Take(pageSize), pageNumber, pageSize, totalAssessments.Count());
            }
            else
            {
                assessments = queryAssessments.ToPagedList(pageNumber, pageSize);
            }

            //Assigns the employees and periods using the list in memory to avoid more accesses to database
            foreach (Assessment assessment in assessments)
            {
                if (assessment.ID == 0)
                {
                    assessment.Employee = new Employee();
                    assessment.Employee.FirstName = ((Employee)user).FirstName;
                    assessment.Employee.FirstLastName = ((Employee)user).FirstLastName;
                    assessment.Employee.SecondLastName = ((Employee)user).SecondLastName;
                }
                else
                {
                    assessment.Employee = employeeList.FirstOrDefault(e => e.ID == assessment.IdEmployee);
                }
                assessment.AssessmentPeriod = periodList.FirstOrDefault(p => p.ID == assessment.IdAssessmentPeriod);
                assessment.FromDate = assessment.AssessmentPeriod.FromDate;
                assessment.ToDate = assessment.AssessmentPeriod.ToDate;

                assessment.EmployeeComments = assessment.EmployeeComments.Truncate(100);
                assessment.ManagerComments = assessment.ManagerComments.Truncate(100);
            }
            ViewBag.userType = user.UserType;
            return View(assessments);
        }

        [Authorize]
        public ActionResult Create(int periodId)
        {
            UserModel user = UserModel.GetCurrentUser();
            if (user != null && user.UserType == UserType.Employee)
            {
                //Validates that the assessment is unique for an user in one period
                int assessmentId = (from a in db.Assessments
                                    where a.IdAssessmentPeriod == periodId && a.IdEmployee == user.ID
                                    select a.ID).FirstOrDefault();//db.Assessments.Where(a => a.IdAssessmentPeriod == periodId && a.IdEmployee == employee.ID).FirstOrDefault();
                //Creates the new assessment
                if (assessmentId == 0)
                {
                    assessmentId = Assessment.StartNewAssessment(periodId, user.ID, ((Employee)user).IdManager);
                }
                return RedirectToAction("Details", new { assessmentId = assessmentId });
            }
            return RedirectToAction("Index", "Assessments");
        }

        [Authorize]
        public ActionResult Details(int assessmentId)
        {
            Assessment assessment = db.Assessments.Include("Employee").Include("Manager").Include("Employee.Team").Include("AssessmentPeriod").FirstOrDefault(a => a.ID == assessmentId);
            UserModel user = UserModel.GetCurrentUser();
            if (assessment.AccessDenied(user))
                throw new AccessDeniedException();
            ViewBag.userType = user.UserType;
            if (user.UserType == UserType.Administrator)
            {
                var employeeList = db.Employees.OrderBy(x => x.FirstName).ToList();
                var employees = from u in employeeList
                                select new EmployeeList
                                {
                                    ID = u.ID,
                                    Name = u.FirstName + " " + u.FirstLastName + " " + u.SecondLastName,
                                };
                ViewBag.employees = new SelectList(employees, "ID", "Name");
                var managerList = db.Managers.OrderBy(x => x.FirstName).ToList();
                var managers = from u in managerList
                                select new EmployeeList
                                {
                                    ID = u.ID,
                                    Name = u.FirstName + " " + u.LastName
                                };
                ViewBag.managers = new SelectList(managers, "ID", "Name");
            }
            return View(assessment);
        }
        [Authorize]
        [HttpPost]
        public JsonResult AdminEditor(AdminEditorViewModel model )
        {
            var assessment = db.Assessments.Include("Employee").Include("Manager").Include("Employee.Team").FirstOrDefault(a => a.ID == model.AssessmentId);
            var newFromDate = model.From ?? assessment.FromDate;
            if (newFromDate != assessment.FromDate)
            {
                assessment.FromDate = newFromDate;
            }
            var newToDate = model.To ?? assessment.ToDate;
            if (newToDate != assessment.ToDate)
            {
                assessment.ToDate = newToDate;
            }
            db.SaveChanges();
            if (model.SkillId != null)
            {
            var skill = db.Skills.FirstOrDefault(s => s.ID == model.SkillId);
            skill.Name = model.SkillName;
                skill.Description = model.SkillDescription;
                db.SaveChanges();
            }
            return Json(new {data="ok"});
        }

        [Authorize]
        public ActionResult Edit(int assessmentId)
        {
            var assessment = db.Assessments.Include("Employee").Include("Manager").Include("Employee.Team").FirstOrDefault(a => a.ID == assessmentId);
            UserModel user = UserModel.GetCurrentUser();
            if (assessment != null && assessment.AccessDenied(user))
                throw new AccessDeniedException();
            ViewBag.userType = user.UserType;
            return View(assessment);
        }

        [HttpPost]
        [Authorize]
        public ActionResult Edit(Assessment assessment)
        {
            if (ModelState.IsValid)
            {
                //Validates user and bring old data to save only proper changes
                UserModel user = UserModel.GetCurrentUser();
                if (assessment.AccessDenied(user))
                    throw new AccessDeniedException();
                
                Assessment saveAssessment = db.Assessments.FirstOrDefault(a => a.ID == assessment.ID);
                if (assessment.EditionAllowed(user.UserType, UserType.Employee))
                    saveAssessment.EmployeeComments = assessment.EmployeeComments;
                else if (assessment.EditionAllowed(user.UserType, UserType.Manager))
                    saveAssessment.ManagerComments = assessment.ManagerComments;
                db.Entry(saveAssessment).State = EntityState.Modified;
                db.SaveChanges();
            }
            return RedirectToAction("Details", new { assessmentId = assessment.ID });
        }

        [Authorize]
        [HttpPost]
        public ActionResult Validate(int assessmentId)
        {
            Assessment assessment = db.Assessments.Include("Employee").Include("Manager").Where(a => a.ID == assessmentId).FirstOrDefault();
            UserModel user = UserModel.GetCurrentUser();
            if (assessment.AccessDenied(user))
                throw new AccessDeniedException();

            switch (assessment.ValidateAssessment(user.UserType))
            {
                case AssessmentValidationResult.MissingGoals:
                    return Json(new { valid = false, error = GetError(AssessmentValidationResult.MissingGoals, user.UserType, assessmentId)});
                case AssessmentValidationResult.MissingScores:
                    return Json(new { valid = false, error = GetError(AssessmentValidationResult.MissingScores, user.UserType, assessmentId)});
            }

            if (user.UserType == UserType.Employee && assessment.IdAssessmentStatus == (int)AssessmentStatus.Disagreement && assessment.CheckForDisagreement())
                return Json(new { valid = false, error = GetError(AssessmentValidationResult.Disagreement, user.UserType, assessmentId)});

            return Json(new { valid = true, error = string.Empty });
        }

        [Authorize]
        public ActionResult Accept(int assessmentId)
        {
            Assessment assessment = db.Assessments.Include("Employee").Include("Manager").Where(a => a.ID == assessmentId).FirstOrDefault();
            UserModel user = UserModel.GetCurrentUser();
            if (assessment.AccessDenied(user))
                throw new AccessDeniedException();

            ViewBag.assessmentStatus = (AssessmentStatus)assessment.IdAssessmentStatus;
            return View(assessment);
        }

        [Authorize]
        [HttpPost]
        public ActionResult Accept(Assessment assessment)
        {
            Assessment saveAssessment = db.Assessments.Where(a => a.ID == assessment.ID).FirstOrDefault();
            UserModel user = UserModel.GetCurrentUser();
            if (assessment.AccessDenied(user))
                throw new AccessDeniedException();

            EmailUtil emailToSend = null;

            if (assessment.EditionAllowed(user.UserType, UserType.Employee))
            {
                if (assessment.IdAssessmentStatus == (int)AssessmentStatus.AutoEvaluationOpened)
                {
                    saveAssessment.IdAssessmentStatus = (int)AssessmentStatus.AutoEvaluationCompleted;
                    emailToSend = saveAssessment.GetAutoevaluationCompleteEmail();
                }
                else if (assessment.IdAssessmentStatus == (int)AssessmentStatus.Disagreement)
                {
                    if (!assessment.CheckForDisagreement())
                    {
                        saveAssessment.IdAssessmentStatus = (int)AssessmentStatus.DisagreementSolved;
                        emailToSend = saveAssessment.GetDissagreementSolvedEmail();
                    }
                }
            }
            else if (assessment.EditionAllowed(user.UserType, UserType.Manager))
            {
                if (assessment.IdAssessmentStatus == (int)AssessmentStatus.AutoEvaluationApproved)
                {
                    if (assessment.CheckForDisagreement())
                    {
                        saveAssessment.IdAssessmentStatus = (int)AssessmentStatus.Disagreement;
                        emailToSend = saveAssessment.GetDissagreementFoundEmail();
                    }
                    else
                    {
                        saveAssessment.IdAssessmentStatus = (int)AssessmentStatus.ManagerAssessmentCompleted;
                        emailToSend = saveAssessment.GetManagerEvaluationCompleteEmail();
                    }
                }
            }
            else if (assessment.EditionAllowed(user.UserType, UserType.Administrator))
            {
                switch ((AssessmentStatus)assessment.IdAssessmentStatus)
                {
                    case AssessmentStatus.AutoEvaluationCompleted:
                        saveAssessment.IdAssessmentStatus = (int)AssessmentStatus.AutoEvaluationApproved;
                        emailToSend = saveAssessment.GetManagerEvaluationOpenEmail();
                        break;
                    case AssessmentStatus.DisagreementSolved:
                        saveAssessment.IdAssessmentStatus = (int)AssessmentStatus.ManagerAssessmentCompleted;
                        emailToSend = saveAssessment.GetManagerEvaluationCompleteEmail();
                        break;
                    case AssessmentStatus.ManagerAssessmentCompleted:
                        saveAssessment.IdAssessmentStatus = (int)AssessmentStatus.Completed;
                        break;
                }
            }
            else
            {
                throw new InvalidOperationException("The selected operation cannot be performed on the assessment with its current status.");
            }

            db.Entry(saveAssessment).State = EntityState.Modified;
            db.SaveChanges();

            //Sends email after saving changes since saving is priority
            if (emailToSend != null)
                emailToSend.Send();

            return RedirectToAction("Details", new { assessmentId = assessment.ID });
        }

        [Authorize(Roles = "Administrator")]
        public ActionResult Reject(int assessmentId)
        {
            Assessment assessment = db.Assessments.Where(a => a.ID == assessmentId).FirstOrDefault();
            return View(assessment);
        }

        [Authorize(Roles="Administrator")]
        [HttpPost]
        public ActionResult Reject(int assessmentId, string comments)
        {
            Assessment assessment = db.Assessments.Where(a => a.ID == assessmentId).FirstOrDefault();
            if (assessment.EditionAllowed(UserType.Administrator, UserType.Administrator))
            {
                EmailUtil emailToSend = null;
                switch ((AssessmentStatus)assessment.IdAssessmentStatus)
                {
                    case AssessmentStatus.AutoEvaluationCompleted:
                        assessment.IdAssessmentStatus = (int)AssessmentStatus.AutoEvaluationOpened;
                        emailToSend = assessment.GetAutoevaluationRejectEmail(comments);
                        break;
                    case AssessmentStatus.ManagerAssessmentCompleted:
                        assessment.IdAssessmentStatus = (int)AssessmentStatus.AutoEvaluationApproved;
                        emailToSend = assessment.GetManagerEvaluationRejectEmail(comments);
                        break;
                    case AssessmentStatus.DisagreementSolved:
                        //It returns again to disagreement even if there is no disagreement to avoid sending repeated 
                        //notifications to the manager
                        assessment.IdAssessmentStatus = (int)AssessmentStatus.Disagreement;
                        emailToSend = assessment.GetAutoevaluationRejectEmail(comments);
                        break;
                }
                db.Entry(assessment).State = EntityState.Modified;
                db.SaveChanges();

                if (emailToSend != null)
                    emailToSend.Send();
            }
            else
            {
                throw new InvalidOperationException("The selected operation cannot be performed on the assessment with its current status.");
            }
            return RedirectToAction("Details", new { assessmentId = assessmentId });
        }

        [Authorize]
        public ActionResult Meeting(int assessmentId)
        {
            Assessment assessment = db.Assessments.Include("Employee").Include("Manager").Include("AssessmentPeriod").Where(a => a.ID == assessmentId).FirstOrDefault();
            Meeting meeting = new Meeting();
            meeting.Attendants = string.Format("{0}, {1}", assessment.Employee.Email, assessment.Manager.Email);
            meeting.Subject = string.Format("Assessment Review - {0}", assessment.AssessmentPeriod.Description);
            ViewBag.assessmentId = assessmentId;
            return View(meeting);
        }

        [Authorize]
        [HttpPost]
        public ActionResult Meeting(Meeting meeting)
        {
            if (ModelState.IsValid)
            {
                EmailUtil.CreateiCalAppointment(meeting.Description, meeting.Attendants.Replace(';', ','), string.Empty, meeting.Subject,
                    meeting.Location, meeting.Subject, meeting.Date, meeting.Date.AddMinutes(meeting.Duration));
                return RedirectToAction("Index");
            }
            return View(meeting);
        }

        [NonAction]
        private string GetError(AssessmentValidationResult errorType, UserType userType, int assessmentId)
        {
            string errorMessage = string.Empty;
            switch (errorType)
            {
                case AssessmentValidationResult.MissingScores:
                    errorMessage = "There are some missing scores or comments in the Skills Evaluation. Please fill all of them before submitting the assessment.";
                    break;
                case AssessmentValidationResult.MissingGoals:
                    if (userType == UserType.Employee)
                        errorMessage = "The Goals and Objectives were not filled completely. Please complete this task before submitting the assessment.";
                    else
                        errorMessage = "Some Goals and Objectives have not been reviewed. Please fill comments for all the Goals before submitting the assessment.";
                    break;
                case AssessmentValidationResult.Disagreement:
                    errorMessage = "The assessment has some scores with disagreements between the Employee and Manager. Please make sure that there is a difference of 1 or less points in all the scores.";
                    break;
            }
            return errorMessage;
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}
